import { resolve } from "node:path"
import grpc from "@grpc/grpc-js"
import { loadProto } from "../utils/loadProto.js"

function unaryEchoCall({ client, requestId, name, expectedCode }) {
  return new Promise((resolve, reject) => {
    const call = client.unaryEcho({ name }, (error, res) => {
      if (error) {
        if (error.code === expectedCode) {
          console.log(`[${requestId}] Received error ${error.message}`)
        } else {
          console.log(
            `[${requestId}] Received unexpected error ${error.message}`
          )
        }
      }
      if (res) {
        console.log(`[${requestId}] Received response ${res.value}`)
      }
      resolve()
    })

    /*****************************************************
     * 在一元模式下，以下事件监听不会触发，只执行上述回调函数。
     *****************************************************/

    call.on("status", (status) => {
      console.log(
        `[${requestId}] Received status with code=${status.code} details=${status.details}`
      )
    })

    call.on("end", () => {
      console.log(`[${requestId}] Received end`)
    })

    call.on("error", (error) => {
      console.log(`[${requestId}] Received unexpected error ${error.message}`)
    })
  })
}

function streamingCall({ client, requestId, name, expectedCode }) {
  console.log(`[${requestId}] Calling ServerStreamingEcho with name:"${name}"`)

  return new Promise((resolve, reject) => {
    const call = client.ServerStreamingEcho({ name })

    call.on("data", (data) => {
      console.log(`[${requestId}] Received response ${data.value}`)
    })

    // status 事件，不管成功和失败，都会执行。code 为对应状态码，detail 默认 grpc.status[status.code]，但会被 emit 中 detail 覆盖
    call.on("status", (status) => {
      console.log(
        `[${requestId}] Received status with code=${status.code} details=${status.details}`
      )
      resolve()
    })

    // end 事件，官网描述”事件表示服务器已完成发送，未发生任何错误。只会发出 'error' 或 'end' 中的一个。“
    // 但实际效果即使服务端在 emit error 之后没有再 emit end ，error 事件也会触发 end
    call.on("end", () => {
      console.log(`[${requestId}] Received end`)
    })

    // 'error' 事件表示发生错误并且流已关闭。
    call.on("error", (error) => {
      if (error.code === expectedCode) {
        console.log(`[${requestId}] Received expected error ${error.message}`) // message 即服务器 emit 中的 detail 值
      } else {
        console.log(`[${requestId}] Received unexpected error ${error.message}`)
      }
    })
  })
}

async function main() {
  const PROTO_PATH = resolve(process.cwd(), "./echo.proto")
  const echoProtoDescriptor = loadProto(PROTO_PATH)
  const echoProto = echoProtoDescriptor.echo
  const client = new echoProto.Echo(
    "localhost:50051",
    grpc.credentials.createInsecure()
  )

  await unaryEchoCall({
    client,
    requestId: 1,
    name: "",
    expectedCode: grpc.status.INVALID_ARGUMENT,
  })
  console.log("==============================")
  await unaryEchoCall({
    client,
    requestId: 2,
    name: "tom",
    expectedCode: grpc.status.OK,
  })
  console.log("==============================")
  await streamingCall({
    client,
    requestId: 3,
    name: "",
    expectedCode: grpc.status.INVALID_ARGUMENT,
  })
  console.log("==============================")
  await streamingCall({
    client,
    requestId: 4,
    name: "tom",
    expectedCode: grpc.status.OK,
  })
}

main()
